using System;
using ch.ethz.ssh2.log;
using System.Diagnostics;
using System.Threading;
namespace ch.ethz.ssh2.util
{

    /// <summary> TimeoutService (beta). Here you can register a timeout.
    /// <p>
    /// Implemented having large scale programs in mind: if you open many concurrent SSH connections
    /// that rely on timeouts, then there will be only one timeout thread. Once all timeouts
    /// have expired/are cancelled, the thread will (sooner or later) exit.
    /// Only after new timeouts arrive a new thread (singleton) will be instantiated.
    /// 
    /// </summary>
    /// <author>  Christian Plattner
    /// </author>
    /// <version>  2.50, 03/15/10
    /// </version>
    public class TimeoutService
    {
        //UPGRADE_NOTE: Final was removed from the declaration of 'log '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
        //UPGRADE_NOTE: The initialization of  'log' was moved to static method 'ch.ethz.ssh2.util.TimeoutService'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
        private static readonly Logger log;

        public class TimeoutToken : System.IComparable
        {
            internal long runTime;
            internal Action handler;

            internal TimeoutToken(long runTime, Action handler)
            {
                this.runTime = runTime;
                this.handler = handler;
            }

            public virtual int CompareTo(System.Object o)
            {
                TimeoutToken t = (TimeoutToken)o;
                if (runTime > t.runTime)
                    return 1;
                if (runTime == t.runTime)
                    return 0;
                return -1;
            }
        }

        /* The list object is also used for locking purposes */
        //UPGRADE_NOTE: Final was removed from the declaration of 'todolist '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
        //UPGRADE_TODO: Class 'java.util.LinkedList' was converted to 'System.Collections.ArrayList' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilLinkedList'"
        private static readonly System.Collections.ArrayList todolist = new System.Collections.ArrayList();

        private static Thread timeoutThread = null;

        /// <summary> It is assumed that the passed handler will not execute for a long time.
        /// 
        /// </summary>
        /// <param name="runTime">
        /// </param>
        /// <param name="handler">
        /// </param>
        /// <returns> a TimeoutToken that can be used to cancel the timeout.
        /// </returns>
        public static TimeoutToken addTimeoutHandler(long runTime, Action handler)
        {
            TimeoutToken token = new TimeoutToken(runTime, handler);

            //lock (todolist.SyncRoot)
            lock (todolist)
            {
                todolist.Add(token);
                todolist.Sort();

                if (timeoutThread != null)
                    timeoutThread.Interrupt();
                else
                {
                    timeoutThread = new Thread(new ThreadStart(TimeoutThread));
                    timeoutThread.IsBackground = true;
                    timeoutThread.Start();
                }
            }

            return token;
        }

        private static void TimeoutThread()
        {
            //lock (TimeoutService.todolist.SyncRoot)
            lock (todolist)
            {
                while (true)
                {
                    if (TimeoutService.todolist.Count == 0)
                    {
                        TimeoutService.timeoutThread = null;
                        return;
                    }

                    long now = (System.DateTime.Now.Ticks - 621355968000000000) / 10000;

                    TimeoutToken tt = (TimeoutToken)TimeoutService.todolist[0];

                    if (tt.runTime > now)
                    {
                        /* Not ready yet, sleep a little bit */

                        try
                        {
                            System.Threading.Monitor.Wait(todolist, TimeSpan.FromMilliseconds(tt.runTime - now));
                        }
                        catch (System.Threading.ThreadInterruptedException e)
                        {
                            Thread.CurrentThread.Interrupt();
                        }

                        /* We cannot simply go on, since it could be that the token
                        * was removed (cancelled) or another one has been inserted in
                        * the meantime.
                        */

                        continue;
                    }

                    TimeoutService.todolist.RemoveAt(0);

                    try
                    {
                        if (null != tt.handler)
                            tt.handler();
                    }
                    catch (System.Exception e)
                    {
                        //Trace.WriteLine(e);
                        //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Throwable.getMessage' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
                        TimeoutService.log.log(20, "Exeception in Timeout handler:" + e.Message);//+ "(" + sw.ToString() + ")");
                    }
                }
            }
        }

        public static void cancelTimeoutHandler(TimeoutToken token)
        {
            //lock (todolist.SyncRoot)
            lock (todolist)
            {
                todolist.Remove(token);

                if (timeoutThread != null)
                    timeoutThread.Interrupt();
            }
        }
        static TimeoutService()
        {
            log = Logger.getLogger(typeof(TimeoutService));
        }
    }
}